package gov.dhs.cis.soa.xslt;

import gov.dhs.cis.soa.Utils;
import gov.dhs.cis.soa.data.Actor;
import gov.dhs.cis.soa.data.Description;
import gov.dhs.cis.soa.data.Service;
import gov.dhs.cis.soa.data.ServiceOperation;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Iterator;

import javax.sql.rowset.serial.SerialArray;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class ReadRSAXMIFile {
	String ACTR_ROOT_XPATH = "//packagedElement[@name='001_Actors']";
	String ACTR_XPATH = "packagedElement[@xmi:type='uml:Actor']";

	String SERV_ROOT_XPATH = "//packagedElement[@name='007_Services']";
	String SERV_XPATH = "packagedElement[@xmi:type='uml:Class']";

	String XMI_NS = "http://schema.omg.org/spec/XMI/2.1";
	
	XPathFactory xpathFactory = XPathFactory.newInstance();
	
	private NamespaceContext getNameSpaceCtx () {
		NamespaceContext ctx = new NamespaceContext() {
	        public String getNamespaceURI(String prefix) {
	        	//System.out.println("Name space requested : " + prefix);
	            String uri = null;
	            if (prefix.equals("xmi")) {
	                uri = XMI_NS;
	            }
	            return uri;
	        }
	       
	        // Dummy implementation - not used!
	        public Iterator getPrefixes(String val) { return null; }
	       
	        // Dummy implemenation - not used!
	        public String getPrefix(String uri) { return null; }
	    };
	    return ctx;
	}
	
	private XPathExpression getXPathExpression(String xpathStr) throws Exception {
		XPath xpath = xpathFactory.newXPath();
		xpath.setNamespaceContext(getNameSpaceCtx());
		// XPath Query for showing all nodes value
		XPathExpression expr = xpath.compile(xpathStr);
		return expr;
	}

	private NodeList evaluate(Object evalOn, XPathExpression expr) throws Exception {
		Object result = expr.evaluate(evalOn, XPathConstants.NODESET);
		NodeList nodes = (NodeList) result;
		return nodes;
	}
	
	public void transform(String inXML) throws Exception {
		long startTime = System.currentTimeMillis();
		Document doc = getXMLDocument(inXML);

		ArrayList<Actor> actorList = extractActor(doc);
		ArrayList<Service> servList = extractServices(doc);

		printXML(actorList, servList);
		
		System.out.println("Time took : " + (System.currentTimeMillis() - startTime)/1000);
	}

	private ArrayList<Actor> extractActor(Document doc) throws Exception {
		ArrayList<Actor> actorList = new ArrayList<Actor>();

		NodeList nodes = evaluate(doc, getXPathExpression(ACTR_ROOT_XPATH));
		Node actorRootFolderNode = nodes.item(0);
		
		nodes = evaluate(actorRootFolderNode, getXPathExpression(ACTR_XPATH));
		
		Node actorNode = null;

		XPathExpression stereoTypeExpr = getXPathExpression("eAnnotations/details/@key");
		XPathExpression descExpr = getXPathExpression("ownedComment/body/text()");
		
		for (int i = 0; i < nodes.getLength(); i++) {
			Actor actor = new Actor();
			actor.desc = new Description();
			
			actorNode = nodes.item(i);
			actor.name = actorNode.getAttributes().getNamedItem("name").getNodeValue();

			NodeList servAttrbNodes = evaluate(actorNode, stereoTypeExpr);
			actor.roleType = servAttrbNodes.item(0).getNodeValue();
			
			servAttrbNodes = evaluate(actorNode, descExpr);
			if(servAttrbNodes.getLength() > 0)
				actor.desc.descTxt = servAttrbNodes.item(0).getNodeValue();

			actorList.add(actor);
		}
		return actorList;
	}

	private ArrayList<Service> extractServices(Document doc) throws Exception {
		ArrayList<Service> servList = new ArrayList<Service>();

		NodeList nodes = evaluate(doc, getXPathExpression(SERV_ROOT_XPATH));
		Node srvRootFolderNode = nodes.item(0);
		
		nodes = evaluate(srvRootFolderNode, getXPathExpression(SERV_XPATH));
		
		Node servNode = null;

		XPathExpression servObjAttribsExpr = getXPathExpression("ownedAttribute/@name");
		XPathExpression stereoTypeExpr = getXPathExpression("eAnnotations/details/@key");
		XPathExpression descExpr = getXPathExpression("ownedComment/body/text()");
		XPathExpression operExpr = getXPathExpression("ownedOperation");
		XPathExpression opParamExpr = getXPathExpression("ownedParameter");
		
		for (int i = 0; i < nodes.getLength(); i++) {
			Service service = new Service();
			ServicesUtils.initialize(service);
			
			servNode = nodes.item(i);
			//System.out.println(attribMap.getNamedItem("name").getNodeValue());
			service.name = servNode.getAttributes().getNamedItem("name").getNodeValue();
			String sXMI_ID = servNode.getAttributes().getNamedItemNS(XMI_NS, "id").getNodeValue();
			Node attribNode = null;
			NodeList servAttrbNodes = evaluate(servNode, servObjAttribsExpr);
			for(int j = 0; j < servAttrbNodes.getLength(); j++) {
				attribNode = servAttrbNodes.item(j);
				ServicesUtils.parseAttribute(service, attribNode.getNodeValue());
			}
			servAttrbNodes = evaluate(servNode, stereoTypeExpr);
			ServicesUtils.parseStereoType(service, servAttrbNodes.item(0).getNodeValue());
			servAttrbNodes = evaluate(servNode, descExpr);
			ServicesUtils.parseDescription(service, servAttrbNodes.item(0).getNodeValue(), null);
			
			parseAssociations(srvRootFolderNode, service, sXMI_ID);
			//parseOperations(servNode, service, operExpr, opParamExpr);
			parseDependencies(doc, srvRootFolderNode, service, sXMI_ID);
			
			servList.add(service);
		}
		return servList;
	}

	private void parseAssociations(Node srvRootFolderNode, Service service, String sXMI_ID) throws Exception {
		Node attribNode;
		String xp = "//packagedElement[@xmi:type='uml:Association']/*[contains(@xmi:id, '" + sXMI_ID + "')]";
		//System.out.println(xp);
		XPathExpression associationsExpr = getXPathExpression(xp);
		NodeList associationNodes = evaluate(srvRootFolderNode, associationsExpr);
		for(int j = 0; j < associationNodes.getLength(); j++) {
			attribNode = associationNodes.item(j);
			ServicesUtils.parseAssociation(service, attribNode.getAttributes().getNamedItemNS(XMI_NS, "id").getNodeValue());
		}
	}

	private void parseDependencies(Document doc, Node srvRootFolderNode, Service service, String sXMI_ID) throws Exception {
		Node attribNode;
		XPathExpression associationsExpr;
		NodeList associationNodes;
		String deprootxpath = "//packagedElement[(@client='" + sXMI_ID + "') and (@xmi:type='uml:Dependency')]";
		associationsExpr = getXPathExpression(deprootxpath);
		associationNodes = evaluate(srvRootFolderNode, associationsExpr);
		for(int j = 0; j < associationNodes.getLength(); j++) {
			attribNode = associationNodes.item(j);
			String depname = attribNode.getAttributes().getNamedItem("name").getNodeValue();
			String supplierid = attribNode.getAttributes().getNamedItem("supplier").getNodeValue();
			
			String supxpath = "//packagedElement[@xmi:id='" + supplierid + "']";
			XPathExpression depNodeExpr = getXPathExpression(supxpath);
			NodeList depNodeList = evaluate(doc, depNodeExpr);
			for(int k = 0; k < depNodeList.getLength(); k++) {
				Node depNode = depNodeList.item(k);
				ServicesUtils.parseDependency(service, depname, getAttributeValue(depNode, "name"));
			}
		}
	}

	private void parseOperations(Node servNode, Service service, XPathExpression operExpr, XPathExpression opParamExpr) throws Exception {
		Node attribNode;
		NodeList operNodes = evaluate(servNode, operExpr);
		for(int j = 0; j < operNodes.getLength(); j++) {
			attribNode = operNodes.item(j);
			String opName = getAttributeValue(attribNode, "name");

			NodeList operParamNodes = evaluate(attribNode, opParamExpr);
			for(int k = 0; k < operParamNodes.getLength(); k++) {
				Node opParamNode = operParamNodes.item(k);
				String paramName = getAttributeValue(opParamNode,"name");
				String paramDirection = getAttributeValue(opParamNode,"direction");
				
				ServicesUtils.parseOperation(service, opName, paramDirection, paramName);
			}
		}
	}
	
	private String getAttributeValue(Node node, String attribName) {
		String ret = "";
		Node attribNode = node.getAttributes().getNamedItem(attribName);
		if(attribNode != null)
			ret = attribNode.getNodeValue();
		return ret;
		
	}
	
	private Document getXMLDocument(String inXML)
			throws ParserConfigurationException, SAXException, IOException {
		InputStream inXMLStream = this.getClass().getClassLoader().getResourceAsStream(inXML);

		DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
		domFactory.setNamespaceAware(true);
		DocumentBuilder builder = domFactory.newDocumentBuilder();
		Document doc = builder.parse(inXMLStream);
		return doc;
	}

	public void printXML(ArrayList<Actor> actorList, ArrayList<Service> servList) throws Exception {
		DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
		Document document = documentBuilder.newDocument();
		Element rootElement = document.createElement("wbim:model");
		rootElement.setAttribute("xmlns:wbim", "http://www.ibm.com/wbim/bomSchema1.0");
		rootElement.setAttribute("schemaVersion", "6.2.0");
		document.appendChild(rootElement);
		
		Element catalogsElement = (Element) rootElement.appendChild(document.createElement("wbim:catalogs"));
		
		Element rescatalogsElement = (Element) catalogsElement.appendChild(document.createElement("wbim:resourceCatalogs"));
		Element rescatalogElement = createWBIMCatalog(document, rescatalogsElement, "Rsrcs", "Roles");
		createWBIMCatalog(document, rescatalogElement, "Human", "Human Roles");
		createWBIMCatalog(document, rescatalogElement, "System", "System Roles");
		
		Element processcatalogsElement = (Element) catalogsElement.appendChild(document.createElement("wbim:processCatalogs"));
		Element proccatalogElement = createWBIMCatalog(document, processcatalogsElement, "Services", "Services");		
		
		String[] processes = { "AKM", "BCM", "ECM", "IAM", "RFM" };
		for(String procName : processes) {
			Element wiptElement = createWBIMCatalog(document, proccatalogElement, procName, procName);
			/*
			for(Service service : servList) {
				if(service.wipt.equalsIgnoreCase(procName))
						createWBIMCatalog(document, wiptElement, Utils.hash(service.name), service.name);
			}
			*/
		}
		Element classifiercatalogsElement = (Element) catalogsElement.appendChild(document.createElement("wbim:classifierCatalogs"));
		Element classcatalogElement = createWBIMCatalog(document, classifiercatalogsElement, "Clssfrs", "Classifiers");
		
		Element resourceModelElement = (Element) rootElement.appendChild(document.createElement("wbim:resourceModel"));
		Element rolesElement = (Element) resourceModelElement.appendChild(document.createElement("wbim:roles"));
		for(Actor actor : actorList)
			createRole(document, rolesElement, actor);
		
		Element processModelElement = (Element) rootElement.appendChild(document.createElement("wbim:processModel"));
		Element servicesElement = (Element) processModelElement.appendChild(document.createElement("wbim:services"));
		for(Service service : servList) {
			if("A".equalsIgnoreCase(service.release))
				createService(document, servicesElement, service);
		}
		
		Element clsModelElement = (Element) rootElement.appendChild(document.createElement("wbim:classifierModel"));
		Element classfiersElement = (Element) clsModelElement.appendChild(document.createElement("wbim:classifiers"));
		Element classfierElement = (Element) classfiersElement.appendChild(document.createElement("wbim:classifier"));
		Element classValueElement = (Element) classfierElement.appendChild(document.createElement("wbim:classifierValue"));
		classValueElement.setAttribute("name", "Srvc32Ty##External");
		classValueElement = (Element) classfierElement.appendChild(document.createElement("wbim:classifierValue"));
		classValueElement.setAttribute("name", "Srvc32Ty##Internal");
		
		transform2Txt(document, Configuration.outFolder + "/wmb_services1.xml");
	}
	
	private void transform2Txt(Document document, String fileName) throws Exception {
	    TransformerFactory transformerFactory = TransformerFactory.newInstance();
        Transformer transformer = transformerFactory.newTransformer();
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
        DOMSource source = new DOMSource(document);
        OutputStream fos = null;
        if(!Utils.isEmpty(fileName)) {
        	fos = new  FileOutputStream(fileName);
        } else {
        	fos = System.out;       	
        }
    	StreamResult result =  new StreamResult(fos);
        transformer.transform(source, result);
	}

	private Element createWBIMCatalog(Document document, Element parentElement, String idValue, String nameValue) {
		Element rescatalogElement = (Element) parentElement.appendChild(document.createElement("wbim:catalog"));
		rescatalogElement.setAttribute("id", idValue);
		rescatalogElement.setAttribute("name", nameValue);
		return rescatalogElement;
	}
	
	private Element createRole(Document document, Element parentElement, Actor actor) {
		Element rescatalogElement = (Element) parentElement.appendChild(document.createElement("wbim:role"));
		if("USCIS System".equalsIgnoreCase(actor.name) || "System".equalsIgnoreCase(actor.roleType))
			rescatalogElement.setAttribute("name", "System##" + actor.name);
		else
			rescatalogElement.setAttribute("name", "Human##" + actor.name);
		Element docElement = (Element) rescatalogElement.appendChild(document.createElement("wbim:documentation"));
		docElement.appendChild(document.createTextNode(Utils.isEmpty(actor.desc.descTxt) ? "" : actor.desc.descTxt));
		return rescatalogElement;
	}

	private Element createService(Document document, Element parentElement, Service service) {
		Element rescatalogElement = (Element) parentElement.appendChild(document.createElement("wbim:service"));
		rescatalogElement.setAttribute("name", ServicesUtils.getNameForWBM(service, null));
		Element docElement = (Element) rescatalogElement.appendChild(document.createElement("wbim:description"));
		docElement.appendChild(document.createTextNode(ServicesUtils.getDescForWBM(service)));
		
		Element classifersElement = (Element) rescatalogElement.appendChild(document.createElement("wbim:classifiers"));
		Element clasElement = (Element) classifersElement.appendChild(document.createElement("wbim:classifierValue"));
		
		String externalSys = ServicesUtils.getExtsystem(service);
		clasElement.setAttribute("name", "Srvc32Ty##" + ("NONE".equalsIgnoreCase(externalSys) ? "Internal" : "External"));
		return rescatalogElement;
	}
	
	private Element createService_opNames(Document document, Element parentElement, Service service) {
		Element rescatalogElement = null;
		String sHash = Utils.hash(service.name);
		for(ServiceOperation sOp : service.opList) {
			rescatalogElement = (Element) parentElement.appendChild(document.createElement("wbim:service"));
			rescatalogElement.setAttribute("name", sHash + "##" + sOp.name);
		}
		return rescatalogElement;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		String modelXMIFIle = "USCISIOEModel1.uml";
		
		ReadRSAXMIFile readFile = new ReadRSAXMIFile();
		try {
			readFile.transform(modelXMIFIle);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
